/**
 * Copyright © 2012, 2013 dr. ir. Jeroen M. Valk
 * 
 * This file is part of ComPosiX.
 * 
 * ComPosiX is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * ComPosiX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * ComPosiX. If not, see <http://www.gnu.org/licenses/>.
 */

var ComPosiX = (function() {

	function Exec(cpx, pid, fd) {
		var argv = new Array();
		var fdv = new Array();

		getargv();

		function getargv() {
			cpx.get(fd[0], function(msg) {
				if (typeof msg == 'string') {
					argv.push(msg);
					getargv();
				} else {
					getfdv(msg);
				}
			});
		}

		function getfdv(msg) {
			fdv.push(msg);
			cpx.get(fd[0], function(msg) {
				if (typeof msg == 'string') {
					var symbol = msg.charAt(0).toUpperCase() + msg.substr(1);
					var pid = cpx.exec(symbol, fdv, argv);
					if (pid == null) {
						load(msg, iterate);
					} else {
						iterate(pid);
					}
				} else {
					getfdv(msg);
				}
			});
		}

		function iterate(pid) {
			cpx.put(fd[1], pid);
			cpx.flush(fd[1]);
			argv = new Array();
			fdv = new Array();
			getargv();
		}

		function load(cmd, cont) {
			var symbol = cmd.charAt(0).toUpperCase() + cmd.substr(1);
			cpx.put(fd[4], [ 'getfile', '/usr/bin/' + cmd + '.js' ]);
			cpx.flush(fd[4]);
			cpx.read(fd[3], 2, function(msgv) {
				if (msgv[0]['MIME-Version'] == '1.0') {
					cpx.put(fd[4], [ 'load', symbol, msgv[1], cpx ]);
					cpx.flush(fd[4]);
					cpx.get(fd[3], function(response) {
						if (response) {
							var result = cpx.exec(symbol, fdv, argv);
							if (pid == null) {
								cpx.put(fd[2], 'exec: system error\n');
								cpx.flush(fd[2]);
							} else {
								cont(result);
							}
						} else {
							cont(null);
						}
					});
				} else {
					cont(null);
				}
			});
		}
	}

	function Decode(cpx, pid, fd, argv) {
		continuation();

		function text() {
			cpx.get(fd[0], function(data) {
				if (data == null) {
					cpx.close(fd[1]);
				} else {
					var i = 0;
					for ( var j = 0; j < data.length; ++j) {
						if (data.charAt(j) == '\n') {
							if (data.charAt(j - 1) == '\r') {
								cpx.put(fd[1], data.substring(i, j - 1));
								cpx.flush(fd[1]);
							} else {
								cpx.put(fd[1], data.substring(i, j));
								cpx.flush(fd[1]);
							}
							i = ++j;
						}
					}
					text();
				}
			});
		}

		function continuation() {
			cpx.get(fd[0], function(header) {
				assert(header['MIME-Version'] == '1.0');
				if (header['Content-Type'][0] == 'text'
						&& header['Content-Type'][1] == 'plain') {
					text();
				} else {
					assert(false);
				}
			});
		}
	}

	function Eof(cpx, pid, fd, argv) {
		var line = '';
		continuation();

		function continuation() {
			cpx.get(fd[0], function(msg) {
				var i = 0;
				for ( var j = 0; j < msg.length; ++j) {
					if (msg.charAt(j) == '\n') {
						if (msg.charAt(j - 1) == '\r') {
							line += msg.substring(i, j - 1);
						} else {
							line += msg.substring(i, j);
						}
						if (/^\*+$/.test(line)) {
							if (line.length == 1) {
								cpx.close(fd[1]);
							} else {
								assert(line.length > 0);
								cpx.put(fd[1], line.substr(1));
								cpx.flush(fd[1]);
							}
						} else {
							cpx.put(fd[1], line);
							cpx.flush(fd[1]);
						}
						line = '';
						i = ++j;
					}
				}
				if (i < msg.length) {
					line += msg.substr(i);
				}
				continuation();
			});
		}
	}

	function Cat(cpx, pid, fd, argv) {
		var nonblank = false, showEnds = false, number = false, squeezeBlank = false;
		var showTabs = false, ignored = false, showNonprinting = false, exit = false;
		var n = 1;
		var lines = '';

		function putline(line) {
			lines += line;
			lines += '\n';
		}

		for ( var i = 0; !exit && i < argv.length; ++i) {
			if (argv[i].charAt(0) == '-') {
				if (argv[i].charAt(1) == '-') {
					switch (argv[i]) {
					case '--show-all':
						showEnds = true;
						showTabs = true;
						showNonprinting = true;
						break;
					case '--number-nonblank':
						numberNonblank = true;
						break;
					case '--showEnds':
						showEnds = true;
						break;
					case '--number':
						number = true;
						break;
					case '--squeeze-blank':
						squeezeBlank = true;
						break;
					case '--show-tabs':
						showTabs = true;
						break;
					case '--show-nonprinting':
						showNonprinting = true;
						break;
					case '--help':
						putline('Usage: cat [OPTION]... [FILE]...');
						putline('Concatenate FILE(s), or standard input, to standard output.');
						putline('');
						putline('-n, --number               number all output lines');
						putline('');
						putline('With no FILE, or when FILE is -, read standard input.');
						putline('');
						putline('Examples:');
						putline("  cat f - g  Output f's contents, then standard input, then g's content.");
						putline('  cat        Copy standard input to standard output.');
						putline('');
						putline('ComPosiX homepage: <http://composix.googlecode.com/>');
						cpx.put(fd[1], lines);
						lines = '';
						cpx.close(fd[1]);
						exit = true;
						break;
					case '--version':
						putline('cat (GNU coreutils) ${project.version}');
						putline('Packaged by ComPosiX');
						putline('Copyright (C) 2011 agents@work');
						putline('License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.');
						putline('This is free software: you are free to change and redistribute it.');
						putline('There is NO WARRANTY, to the extend permitted by law.');
						putline('');
						putline('Written by Jeroen Valk');
						cpx.put(fd[1], lines);
						lines = '';
						cpx.close(fd[1]);
						exit = true;
						break;
					}
				} else {
					for ( var j = 1; !exit && j < argv[i].length; ++j) {
						switch (argv[i].charAt(j)) {
						case 'A':
							showEnds = true;
							showTabs = true;
							showNonprinting = true;
							break;
						case 'b':
							number = true;
							nonblank = true;
							break;
						case 'e':
							showNonprinting = true;
							showEnds = true;
							break;
						case 'E':
							showEnds = true;
							break;
						case 'n':
							number = true;
							break;
						case 's':
							squeezeBlank = true;
							break;
						case 't':
							showNonprinting = true;
							showTabs = true;
							break;
						case 'T':
							showTabs = true;
							break;
						case 'u':
							ignored = true;
							break;
						case 'v':
							showNonprinting = true;
							break;
						default:
							cpx.put(fd[2], 'cat: unknown option -- '
									+ argv[i].charAt(j) + '\n');
							cpx.put(fd[2],
									"Try `cat --help' for more information.\n");
							cpx.flush(fd[2]);
							cpx.close(fd[1]);
							exit = true;
							break;
						}
					}
				}
			}
		}
		assert(ignored || !ignored);
		if (nonblank || showEnds || squeezeBlank || showTabs || showNonprinting) {
			cpx
					.put(fd[2],
							'Some options are not supported by this version of the cat command.\n');
			cpx.flush(fd[2]);
		}

		if (!exit) {
			continuation();
		}

		function format(n) {
			if (number) {
				var prefix = "";
				var suffix = n.toString();
				for ( var i = 0; i < 6 - suffix.length; ++i) {
					prefix += " ";
				}
				return prefix + suffix + "  ";
			} else {
				return '';
			}
		}

		function continuation() {
			cpx.get(fd[0], function(line) {
				if (line == null) {
					cpx.close(fd[1]);
					cpx.exit(pid, 0);
				} else {
					cpx.put(fd[1], format(n++) + line + '\n');
					cpx.flush(fd[1]);
					continuation();
				}
			});
		}

	}

	function Parse(cpx, pid, fd) {

		continuation();

		function continuation() {
			cpx.put(fd[2], "$ ");
			cpx.flush(fd[2]);
			cpx
					.get(
							fd[0],
							function(line) {
								if (line == null) {
									cpx.close(fd[1]);
								} else {
									if (line == '') {
										continuation();
									} else {
										var pipeline = new Array();
										var listv = line.replace(
												/(^\s*)|(\s*$)/g, '').split(
												/\s*\|\s*/);
										for ( var n = 0; n < listv.length; ++n) {
											var wordv = listv[n].split(/\s+/);
											var argv = [];
											var redir = [];
											for ( var i = 0; i < wordv.length; ++i) {
												if (/<|>/.test(wordv[i])) {
													var aux = wordv[i];
													if (/(<|>)$/.test(aux)) {
														if (++i >= wordv.length) {
															cpx
																	.put(fd[1],
																			"bash: syntax error near unexpected token 'newline'");
															cpx.flush(fd[1]);
															aux = null;
														} else {
															aux += wordv[i];
														}
													}
													if (aux == null) {
														argv = null;
														redir = null;
													} else {
														var match = /^([0-9]*)(<|>)(.*)/
																.exec(aux);
														var newfd = parseInt(match[1]);
														var oldfd = null;
														assert(typeof newfd == 'number');
														assert(match[2] == '<'
																|| match[2] == '>');
														assert(is_string(match[3]));
														if (/^&[0-9]+$/
																.test(match[3])) {
															oldfd = parseInt(match[3]
																	.substr(1));
														} else {
															assert(match[3]
																	.substr(0,
																			1) != '&');
															oldfd = match[3];
														}
														if (isNaN(newfd)) {
															if (match[2] == '<') {
																newfd = 0;
															} else {
																assert(match[2] == '>');
																newfd = 1;
															}
														}
														redir[newfd] = {
															dir : match[2],
															from : oldfd,
															to : newfd
														};
													}
												} else {
													argv.push(wordv[i]);
												}
											}
											assert(argv != null);
											pipeline.push({
												cmd : argv[0],
												argv : argv,
												redir : redir
											});
										}
										cpx.put(fd[3], pipeline);
										cpx.flush(fd[3]);
										cpx.get(fd[4], function(seqnr) {
											continuation();
										});
									}
								}
							});
		}
	}

	function Bash(cpx, pid, fd) {
		var seqnr = 0;

		cpx.get(fd[5], function(msg) {
			if (pid == msg) {
				fd[7] = cpx.open();
				fd[8] = cpx.open();
				fd[9] = cpx.open();
				fd[10] = cpx.open();
				fd[11] = cpx.open();
				cpx.write(fd[6], [ fd[0], fd[9], fd[2], 'eof' ]);
				cpx
						.write(fd[6], [ fd[9], fd[1], fd[2], fd[7], fd[8],
								'parse' ]);
				cpx.write(fd[6], [ fd[10], fd[11], fd[2], 'exec' ]);
				cpx.flush(fd[6]);
				cpx.read(fd[5], 3, function(pidv) {
					iterate();
				});
			} else {
				cpx.exit(pid, 1);
			}
		});

		function wait(pidv, continuation) {
			if (pidv.length == 0) {
				continuation();
			} else {
				var pid = pidv.shift();
				if (pid == null) {
					wait(pidv, continuation);
				} else {
					assert(typeof pid == 'number');
					cpx.waitpid(pid, function(code) {
						wait(pidv, continuation);
					});
				}
			}
		}

		function redirect(cmd, fdv) {
//			cpx.put(fd[2], 'redirect(cmd,fdv):');
//			cpx.put(fd[2], cmd);
//			cpx.put(fd[2], fdv);
//			cpx.flush(fd[2]);
			assert(is_tuple(fdv));
			assert(is_tuple(cmd.redir));
			var fdr = new Array();
			for ( var i = 0; i < cmd.redir.length; ++i) {
				if (typeof cmd.redir[i] == 'undefined') {
					assert(false);
				} else {
					assert(typeof cmd.redir[i].from == 'string');
					var aux = cpx.open();
					fdv[i] = cpx.open();
					fdr.push(aux);
					cpx.write(fd[10], [ aux, fdv[i], fd[2], 'decode' ]);
					cpx.put(fd[4], [ 'getfile', cmd.redir[i].from ]);
					cpx.flush(fd[4]);
					var fdx = fdr[i];
					var fname = cmd.redir[i].from;
					cpx.read(fd[3], 2, function(msgv) {
						if (msgv[0]['MIME-Version'] == '1.0') {
							cpx.write(fdx, msgv);
						} else {
							cpx.put(fd[2], "bash: file '" + fname
									+ "' not found\n");
							cpx.flush(fd[2]);
						}
						cpx.close(fdx);

					});
				}
			}
			cpx.write(fd[6], cmd.argv);
			cpx.write(fd[6], fdv);
			cpx.put(fd[6], cmd.cmd);
			// cpx.flush(fd[10]);
			cpx.read(fd[11], fdr.length, function(pidv) {
				for ( var i = 0; i < fdr.length; ++i) {
					assert(typeof pidv[i] == 'number');
				}
			});
		}

		function forward(fdx) {
			cpx.wait(fdx, function(n) {
				var msgv = cpx.recv(fdx, n);
				if (msgv.length > 0) {
					cpx.write(fd[1], msgv);
					cpx.flush(fd[1]);
					forward(fdx);
				}
			});
		}

		function pipeline(line) {
//			cpx.put(fd[2], 'pipeline(line):');
//			cpx.put(fd[2], line);
//			cpx.flush(fd[2]);
			assert(is_tuple(line));
			var size = line.length;
			if (size > 0) {
				var fdx = fd[9];
				for ( var i = 0; i < size; ++i) {
					var fdy = cpx.open();
					redirect(line[i], [ fdx, fdy, fd[2] ]);
					fdx = fdy;
				}
				cpx.flush(fd[6]);
				cpx.read(fd[5], size, function(pidv) {
					cpx.flush(fd[10]);
					for ( var i = 0; i < size; ++i) {
						if (pidv[i] == null) {
							cpx.put(fd[2], 'bash: ' + line[i].cmd
									+ ': command not found\n');
							cpx.flush(fd[2]);
						} else {
							assert(typeof pidv[i] == 'number' && pidv[i] >= 0);
						}
					}
					forward(fdx);
					wait(pidv, function() {
						try {
							cpx.close(fd[9]);
						} catch (e) {

						}
						cpx.open(fd[9]);
						cpx.put(fd[8], seqnr++);
						cpx.flush(fd[8]);
						iterate();
					});
				});
			} else {
				putline(fd[8], seqnr++);
				flush(fd[8]);
				iterate();
			}
		}

		function iterate() {
			cpx.get(fd[7], function(line) {
				pipeline(line);
			});
		}
	}

	function Initializer(cpx) {
		var oldsize = cpx.size;
		// cpx.logger.level = 'debug';
		cpx.size = 512;
		cpx.open(0, 1, 2, 3, 4);
		cpx.size = oldsize;
		cpx.get(0, function(header) {
			assert(header['MIME-Version'] == '1.0');
			if (header['Content-Type'][0] == 'text'
					&& header['Content-Type'][1] == 'plain') {
				assert(true);
			} else {
				assert(false);
			}
		});
		var fdv = [ 0, 1, 2, 3, 4, cpx.open(), cpx.open() ];
		cpx.exec('Exec', [ fdv[6], fdv[5], 2, 3, 4 ]);
		cpx.write(fdv[6], [ 0, 1, 2, 3, 4, fdv[5], fdv[6], 'bash' ]);
		cpx.flush(fdv[6]);
	}

	return function(me, you, redirect) {
		this.self = new Subordinate(me, you, redirect, this, new Logger(
				'ComPosiX'), 'ComPosiX');

		this.Exec = Exec;
		this.Decode = Decode;
		this.Eof = Eof;
		this.Cat = Cat;
		this.Parse = Parse;
		this.Bash = Bash;
		this.Initializer = Initializer;
	};
})();